 package org.mybatis.builder.xml;
 
 import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.ResultMapResolver;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.builder.xml.XMLStatementBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
 

@SuppressWarnings({"rawtypes","unchecked"})
 public class XMLGenericMapperBuilder extends BaseBuilder
 {
   private XPathParser parser;
   private MapperBuilderAssistant builderAssistant;
   private Map<String, XNode> sqlFragments;
   private String resource;
 
   public XMLGenericMapperBuilder(String xml, Configuration configuration, String resource, Map<String, XNode> sqlFragments)
   {
     this(new XPathParser(xml, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments);
   }
 
   public XMLGenericMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace)
   {
     this(inputStream, configuration, resource, sqlFragments);
     this.builderAssistant.setCurrentNamespace(namespace);
   }
 
   public XMLGenericMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
     this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments);
   }
 
   private XMLGenericMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments)
   {
     super(configuration);
     this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
     this.parser = parser;
     this.sqlFragments = sqlFragments;
     this.resource = resource;
   }
 
   public void parse() {
     if (!this.configuration.isResourceLoaded(this.resource)) {
       configurationElement(this.parser.evalNode("/mapper"));
       this.configuration.addLoadedResource(this.resource);
       bindMapperForNamespace();
     }
 
     parsePendingResultMaps();
     parsePendingChacheRefs();
     parsePendingStatements();
   }
 
   public XNode getSqlFragment(String refid) {
     return (XNode)this.sqlFragments.get(refid);
   }
 
   private void configurationElement(XNode context) {
     try {
       String namespace = context.getStringAttribute("namespace");
       if ((namespace == null) || (namespace.equals(""))) {
         throw new BuilderException("Mapper's namespace cannot be empty");
       }
       this.builderAssistant.setCurrentNamespace(namespace);
       cacheRefElement(context.evalNode("cache-ref"));
       cacheElement(context.evalNode("cache"));
       parameterMapElement(context.evalNodes("/mapper/parameterMap"));
       resultMapElements(context.evalNodes("/mapper/resultMap"));
       sqlElement(context.evalNodes("/mapper/sql"));
       buildAllStatements();
       buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
     } catch (Exception e) {
       throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
     }
   }
 
   /**
    * 添加incompleteStatements里的元素，并清空incompleteStatements
    */
   private void buildAllStatements(){
       this.configuration.hasStatement("id");
       Collection<XMLStatementBuilder> incompleteStatements = this.configuration.getIncompleteStatements();
       for (Iterator<XMLStatementBuilder> iterator = incompleteStatements.iterator(); iterator.hasNext();) {
		iterator.next();
		iterator.remove();  
       }
   }
   
   private void buildStatementFromContext(List<XNode> list) {
     if (this.configuration.getDatabaseId() != null) {
       buildStatementFromContext(list, this.configuration.getDatabaseId());
     }
     buildStatementFromContext(list, null);
   }
 
   private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
     String id = null;
     for (XNode context : list) {
       id = this.builderAssistant.applyCurrentNamespace(context.getStringAttribute("id"), false);
       if (!this.configuration.hasStatement(id,false)){
         XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
         try {
           statementParser.parseStatementNode();
         } catch (IncompleteElementException e) {
           this.configuration.addIncompleteStatement(statementParser);
         }
       }
     }
   }
   
   private void parsePendingResultMaps() { Collection incompleteResultMaps = this.configuration.getIncompleteResultMaps();
     synchronized (incompleteResultMaps) {
       Iterator iter = incompleteResultMaps.iterator();
       while (iter.hasNext())
         try {
           ((ResultMapResolver)iter.next()).resolve();
           iter.remove();
         }
         catch (IncompleteElementException localIncompleteElementException)
         {
         }
     } }
 
   private void parsePendingChacheRefs()
   {
     Collection incompleteCacheRefs = this.configuration.getIncompleteCacheRefs();
     synchronized (incompleteCacheRefs) {
       Iterator iter = incompleteCacheRefs.iterator();
       while (iter.hasNext())
         try {
           ((CacheRefResolver)iter.next()).resolveCacheRef();
           iter.remove();
         }
         catch (IncompleteElementException localIncompleteElementException)
         {
         }
     }
   }
 
   private void parsePendingStatements() {
     Collection incompleteStatements = this.configuration.getIncompleteStatements();
     synchronized (incompleteStatements) {
       Iterator iter = incompleteStatements.iterator();
       while (iter.hasNext())
         try {
           ((XMLStatementBuilder)iter.next()).parseStatementNode();
           iter.remove();
         }
         catch (IncompleteElementException localIncompleteElementException)
         {
         }
     }
   }
 
   private void cacheRefElement(XNode context) {
     if (context != null) {
       this.configuration.addCacheRef(this.builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
       CacheRefResolver cacheRefResolver = new CacheRefResolver(this.builderAssistant, context.getStringAttribute("namespace"));
       try {
         cacheRefResolver.resolveCacheRef();
       } catch (IncompleteElementException e) {
         this.configuration.addIncompleteCacheRef(cacheRefResolver);
       }
     }
   }
 
   private void cacheElement(XNode context) throws Exception {
     if (context != null) {
       String type = context.getStringAttribute("type", "PERPETUAL");
       Class typeClass = this.typeAliasRegistry.resolveAlias(type);
       String eviction = context.getStringAttribute("eviction", "LRU");
       Class evictionClass = this.typeAliasRegistry.resolveAlias(eviction);
       Long flushInterval = context.getLongAttribute("flushInterval");
       Integer size = context.getIntAttribute("size");
       boolean readWrite = !context.getBooleanAttribute("readOnly", Boolean.valueOf(false)).booleanValue();
       boolean blocking = context.getBooleanAttribute("blocking", Boolean.valueOf(false)).booleanValue();
       Properties props = context.getChildrenAsProperties();
       this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
     }
   }
 
   private void parameterMapElement(List<XNode> list) throws Exception {
     for (XNode parameterMapNode : list) {
       String id = parameterMapNode.getStringAttribute("id");
       String type = parameterMapNode.getStringAttribute("type");
       Class parameterClass = resolveClass(type);
       List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
       List parameterMappings = new ArrayList();
       for (XNode parameterNode : parameterNodes) {
         String property = parameterNode.getStringAttribute("property");
         String javaType = parameterNode.getStringAttribute("javaType");
         String jdbcType = parameterNode.getStringAttribute("jdbcType");
         String resultMap = parameterNode.getStringAttribute("resultMap");
         String mode = parameterNode.getStringAttribute("mode");
         String typeHandler = parameterNode.getStringAttribute("typeHandler");
         Integer numericScale = parameterNode.getIntAttribute("numericScale");
         ParameterMode modeEnum = resolveParameterMode(mode);
         Class javaTypeClass = resolveClass(javaType);
         JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
 
         Class typeHandlerClass = resolveClass(typeHandler);
         ParameterMapping parameterMapping = this.builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
         parameterMappings.add(parameterMapping);
       }
       this.builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
     }
   }
 
   private void resultMapElements(List<XNode> list) throws Exception {
     for (XNode resultMapNode : list)
       try {
         resultMapElement(resultMapNode);
       }
       catch (IncompleteElementException localIncompleteElementException)
       {
       }
   }
 
   private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
     return resultMapElement(resultMapNode, Collections.emptyList());
   }
 
   private ResultMap resultMapElement(XNode resultMapNode, List<Object> additionalResultMappings) throws Exception {
     ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
     String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
 
     String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
 
     String extend = resultMapNode.getStringAttribute("extends");
     Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
     Class typeClass = resolveClass(type);
     Discriminator discriminator = null;
     List resultMappings = new ArrayList();
     resultMappings.addAll(additionalResultMappings);
     List<XNode> resultChildren = resultMapNode.getChildren();
     for (XNode resultChild : resultChildren) {
       if ("constructor".equals(resultChild.getName())) {
         processConstructorElement(resultChild, typeClass, resultMappings);
       } else if ("discriminator".equals(resultChild.getName())) {
         discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
       } else {
         List flags = new ArrayList();
         if ("id".equals(resultChild.getName())) {
           flags.add(ResultFlag.ID);
         }
         resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
       }
     }
     ResultMapResolver resultMapResolver = new ResultMapResolver(this.builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
     try {
       return resultMapResolver.resolve();
     } catch (IncompleteElementException e) {
       this.configuration.addIncompleteResultMap(resultMapResolver);
       throw e;
     }
   }
 
   private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
     List<XNode> argChildren = resultChild.getChildren();
     for (XNode argChild : argChildren) {
       List flags = new ArrayList();
       flags.add(ResultFlag.CONSTRUCTOR);
       if ("idArg".equals(argChild.getName())) {
         flags.add(ResultFlag.ID);
       }
       resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
     }
   }
 
   private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<Object> resultMappings) throws Exception {
     String column = context.getStringAttribute("column");
     String javaType = context.getStringAttribute("javaType");
     String jdbcType = context.getStringAttribute("jdbcType");
     String typeHandler = context.getStringAttribute("typeHandler");
     Class javaTypeClass = resolveClass(javaType);
 
     Class typeHandlerClass = resolveClass(typeHandler);
     JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
     Map discriminatorMap = new HashMap();
     for (XNode caseChild : context.getChildren()) {
       String value = caseChild.getStringAttribute("value");
       String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
       discriminatorMap.put(value, resultMap);
     }
     return this.builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
   }
 
   private void sqlElement(List<XNode> list) throws Exception {
     if (this.configuration.getDatabaseId() != null) {
       sqlElement(list, this.configuration.getDatabaseId());
     }
     sqlElement(list, null);
   }
 
   private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
     for (XNode context : list) {
       String databaseId = context.getStringAttribute("databaseId");
       String id = context.getStringAttribute("id");
       id = this.builderAssistant.applyCurrentNamespace(id, false);
       if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId))
         this.sqlFragments.put(id, context);
     }
   }
 
   private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId)
   {
     if (requiredDatabaseId != null) {
       if (!requiredDatabaseId.equals(databaseId))
         return false;
     }
     else {
       if (databaseId != null) {
         return false;
       }
 
       if (this.sqlFragments.containsKey(id)) {
         XNode context = (XNode)this.sqlFragments.get(id);
         if (context.getStringAttribute("databaseId") != null) {
           return false;
         }
       }
     }
     return true;
   }
 
   private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
     String property = context.getStringAttribute("property");
     String column = context.getStringAttribute("column");
     String javaType = context.getStringAttribute("javaType");
     String jdbcType = context.getStringAttribute("jdbcType");
     String nestedSelect = context.getStringAttribute("select");
     String nestedResultMap = context.getStringAttribute("resultMap", processNestedResultMappings(context, Collections.emptyList()));
 
     String notNullColumn = context.getStringAttribute("notNullColumn");
     String columnPrefix = context.getStringAttribute("columnPrefix");
     String typeHandler = context.getStringAttribute("typeHandler");
     String resulSet = context.getStringAttribute("resultSet");
     String foreignColumn = context.getStringAttribute("foreignColumn");
     boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", this.configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
     Class javaTypeClass = resolveClass(javaType);
 
     Class typeHandlerClass = resolveClass(typeHandler);
     JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
     return this.builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resulSet, foreignColumn, lazy);
   }
 
   private String processNestedResultMappings(XNode context, List<Object> resultMappings) throws Exception {
     if (("association".equals(context.getName())) || ("collection".equals(context.getName())) || ("case".equals(context.getName())))
     {
       if (context.getStringAttribute("select") == null) {
         ResultMap resultMap = resultMapElement(context, resultMappings);
         return resultMap.getId();
       }
     }
     return null;
   }
 
   private void bindMapperForNamespace() {
     String namespace = this.builderAssistant.getCurrentNamespace();
     if (namespace != null) {
       Class boundType = null;
       try {
         boundType = Resources.classForName(namespace);
       }
       catch (ClassNotFoundException localClassNotFoundException) {
       }
       if ((boundType != null) && 
         (!this.configuration.hasMapper(boundType)))
       {
         this.configuration.addLoadedResource("namespace:" + namespace);
         this.configuration.addMapper(boundType);
       }
     }
   }
 }

